import asyncio
from typing import Any

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
from starlette.routing import Route, Mount


class Application(FastAPI):

    def __init__(self, **extra: Any):
        super().__init__(**extra)

        self.__env = None
        self.__settings = None
        self.__mgw = None
        self.__mgr = None
        self.__redis = None
        self.__logger = None

    @property
    def settings(self):
        return self.__settings

    @settings.setter
    def settings(self, value):
        self.__settings = value

    @property
    def logger(self):
        return self.__logger

    @logger.setter
    def logger(self, value):
        self.__logger = value

    @property
    def mgw(self):
        return self.__mgw

    @mgw.setter
    def mgw(self, value):
        self.__mgw = value

    @property
    def redis(self):
        return self.__redis

    @redis.setter
    def redis(self, value):
        self.__redis = value


def print_all_router(app, prefix=""):
    from common.g.g import G_LOGGER
    for r in app.routes:
        if isinstance(r, Route):
            G_LOGGER.debug(f"{r.methods}->{prefix}{r.path}->{r.name}")
        elif isinstance(r, APIRoute):
            G_LOGGER.debug(f"{r.methods}->{prefix}{r.path}->{r.name}->{r.summary}")
        elif isinstance(r, Mount):
            print_all_router(r, prefix=r.path)


def register_exception_handler(application, Exc):
    from common.utils.resp import JsonResponse, RC
    @application.exception_handler(Exc)
    async def validation_missing_tenant_handler(request: Request, exc: Exc):
        msg = exc.args
        return JsonResponse(code=RC.PARAM_ERROR, msg=msg).to_response()


def get_main_application():
    main_application = Application()

    tenant_application = Application()
    client_application = Application()
    admin_application = Application()
    openapi_application = Application()

    from common.g.g import init_settings
    settings = init_settings()
    from common.g.g import init_logger
    logger = init_logger()

    @main_application.on_event("startup")
    async def startup():
        loop = asyncio.get_event_loop()
        # 初始化全局变量
        from common.g.g import init_db
        mgw = init_db(loop)

        from common.initialize.init_mgw import init_db
        mgw2 = init_db(loop, settings.mysql_config)

        from common.g.g import init_redis
        redis = init_redis()
        from common.g.g import init_client
        client = await init_client(loop)
        from common.g.g import init_tenant_bloomfilter
        init_tenant_bloomfilter()
        from common.g.g import init_tenant_email_bloomfilter
        init_tenant_email_bloomfilter()
        from common.g.g import init_mall_bloomfilter
        init_mall_bloomfilter()
        from common.g.g import init_rabbitmq_client_pool
        await init_rabbitmq_client_pool(loop, settings.rabbitmq_config)
        from common.initialize.init_table import init_table
        init_table()

        from common.g.g import init_id_worker
        await init_id_worker(settings)
        from common.g.g import init_es_client, init_spu_es_client
        es_client = init_es_client(settings.es_config.dict())
        init_spu_es_client(es_client)

        # 绑定子app
        main_application.mount("/ins", tenant_application, name="tenant")
        main_application.mount("/mgr", admin_application, name="manage")
        main_application.mount("/cli", client_application, name="client")
        main_application.mount("/", openapi_application, name="openapi")
        main_application.mgw = tenant_application.mgw = openapi_application.mgw = client_application.mgw = admin_application.mgw = mgw
        main_application.mgw2 = tenant_application.mgw2 = openapi_application.mgw2 = client_application.mgw2 = admin_application.mgw2 = mgw2
        main_application.redis = tenant_application.redis = openapi_application.redis = client_application.redis = admin_application.redis = redis
        main_application.logger = tenant_application.logger = openapi_application.logger = client_application.logger = admin_application.logger = logger
        main_application.client = tenant_application.client = openapi_application.client = client_application.client = client_application.client = client
        main_application.settings = tenant_application.settings = openapi_application.settings = client_application.settings = client_application.settings = settings
        # 注册路由
        from router.init_router import init_openapi_router, init_client_router, init_admin_router, init_tenant_router, init_testapi_router
        init_tenant_router(tenant_application)
        init_admin_router(admin_application)
        init_client_router(client_application)
        init_openapi_router(openapi_application)
        init_testapi_router(main_application)

        print_all_router(main_application)

        # 注册异常处理
        from common.middleware.tenant_middleware import MissingTenantError

        for app in (main_application, admin_application, tenant_application, client_application, openapi_application):
            register_exception_handler(app, RequestValidationError)
        register_exception_handler(tenant_application, MissingTenantError)

        # 中间件
        from common.middleware.request_id_middleware import TraceMiddleware
        main_application.add_middleware(TraceMiddleware)
        from common.middleware.timing_middleware import TimingMiddleware
        main_application.add_middleware(TimingMiddleware)

        # from common.initialize.init_cache import load_cache_data
        # await load_cache_data()

    @main_application.on_event("shutdown")
    async def shutdown():
        from common.g.g import close_client, close_db, close_redis, clear_id_worker_status, close_rabbitmq_client_pool
        await close_db()
        await close_redis()
        await close_client()
        await clear_id_worker_status()
        await close_rabbitmq_client_pool()

    @main_application.get("/health_check")
    def health_check():
        return "ok"

    return main_application
